
/** 
 * This is a simple udp|tcp|tls echo server for testing aio library 
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>

#ifdef _WIN32
# include <winsock2.h>
#else 
# include <unistd.h>
# include <string.h>
# include <netinet/in.h>
#endif 

#include "aio_base.h"
#include "aio_log.h"
#include "aio_socket.h"
#include "aio_slot.h"
#include "getopt.h"

/* globle variable define */
static char server_address[64] = "0.0.0.0:5678";
static char bindip[16] = {0};
static unsigned short bindport = 0;
static int receive_print = 0;
static int log_level = 1;
static int max_connection_count = 1024;
static char transport[32] = "udp:tcp";
static int use_udp_transport = 1; 
static int use_tcp_transport = 1; 
static int use_tls_transport = 0; 
static char certificate[128] = "ca.crt:server.crt:server.key";
static char cacrt[32] = "ca.crt"; 
static char srvcrt[32] = "server.crt"; 
static char srvkey[32] = "server.key"; 
const char* helpinfo = 
       "\nThis is a simple udp|tcp|tls echo server for testing aio library.\n\n"
       "Usage: echoserver [OPTIONS]\n"
       "    -h,  help       display this message then exit.\n"
       "    -v,  version    display version information then exit.\n"
       "    -d,  debug      debug log onoff\n"
       "    -p,  printf     print receive packet onoff\n"
       "    -b,  bindaddr   bind address, [ip:port], default 0.0.0.0:5678\n"
       "    -c,  capacity   max connection counts, default 1024\n"
       "    -t,  transport  server support transport, [udp:(tcp|tls)], default: udp:tcp\n"
       "    -k,  cert       certificate for openssl, [ca.crt:srv.crt:srv.key]\n" 
       "Report bugs to <hlxxxx@google.com>\n";

void 
onclosed(struct aio_slot *slot)
{
        debugp("Enter onclosed function... \n");
}

void
ontimer1(struct aio_timer *at, void *arg)
{
        debugp("Enter ontimer1 function.\n");
}

void
ontimer2(struct aio_timer *at, void *arg)
{
        debugp("Enter ontimer2 function.\n");
        aio_timer_cancel(at);
}

void 
onwrote(struct aio_slot *slot)
{
        debugp("Enter onwrote function...\n");
}

void 
onreceived(struct aio_slot *slot)
{
        struct aio_buf *rbuf;

        debugp("Enter onread function...\n");
        assert(slot);

        if (SLOTIOERR(slot) != 0) {
                debugp("Slot operate timeout or other error.\n");
                return;
        }

        rbuf = AIO_READ(slot, 1);
        assert(rbuf);

        if (receive_print) {
                *(BEND(rbuf)) = 0;
                printf("%s", BPTR(rbuf));
        }

        AIO_SETDSTADDR3(slot, slot->frmip, slot->frmport);
        AIO_WRITE(slot, rbuf, onwrote);
}

void 
onaccepted(struct aio_slot *slot)
{         
        debugp("Enter onaccepted function...\n");         
        assert(slot);         

        aio_setclose(slot, onclosed);         
        if (aio_setread(slot, onreceived, 1, 0) < 0) {
                log_warn("Failed to set aio read.\n");
                aio_slot_close(slot, 1);
        }
}

static int 
parse_options(int argc, char *argv[])
{
	int c;
	extern char *optarg;
        extern int optind, opterr;

        opterr = 0;

        if (argc == 1) return -1;

	while ((c = getopt(argc, argv, "hvpdb:c:t:k:")) != -1) {
		switch(c) {
                case 'h':
                        printf(helpinfo);
                        return 1;
                case 'v':
                        printf("echoserver-0.0.1\n");
                        return 1;
                case 'p':
                        receive_print = 1;
                        break;
                case 'd':
                        log_level = 0;
                        break;
                case 'b':
                        if (strlen(optarg) > 64) return -1;
                        strcpy(server_address, optarg);
                        break;
                case 'c':
                        max_connection_count = atoi(optarg);
                        break;
                case 't':
                        if (strlen(optarg) > 32) return -1;
                        strcpy(transport, optarg);
                        break;
                case 'k': 
                        if (strlen(optarg) > 128) return -1;
                        strcpy(certificate, optarg);
                        break;
                default:
                        printf("Unknown input arguments [%c].\n", c);
                        return -1;
                }
        }

        if (optind < argc) {
                printf("Unexpected argument %s\n", argv[optind]);
                return -1;
        }

	return 0;
}

int 
main(int argc, char **argv)
{
        struct aio_base *base;
        struct aio_slot *datagramslot, *streamslot;
        unsigned short shutdown = 0;
        char *pos = 0, *pos1 = 0;
        int ret = 0;

#ifdef _WIN32		
        WSADATA wsaData;        
        WORD version = MAKEWORD(2, 0);        
        WSAStartup(version, &wsaData);
#endif
        ret = parse_options(argc, argv);
        if (ret) {
                if (ret == -1) printf(helpinfo);
                exit(-1);
        }

        log_set_silence_level(log_level);

        base = aio_create(NULL, max_connection_count+10);
        if (base == NULL) {
                log_err("Failed to create AIO.\n");
                exit(-1);
        }

        ABUSRP(base) = &shutdown;

        /* get transport */
        pos = strstr(transport, "udp");
        use_udp_transport = pos ? 1 : 0;

        pos = strstr(transport, "tcp");
        use_tcp_transport = pos ? 1 : 0;

        pos = strstr(transport, "tls");
        use_tls_transport = pos ? 1 : 0;

        if (use_tcp_transport && use_tls_transport) {
                log_err("Can't use tcp and tls at the same time.\n");
                exit (-1);
        }

        /* get tls certificate */
        if (use_tls_transport) {
                pos = strstr(certificate, ":");
                if (pos == NULL) {
                        log_err("Invalid ssl certificate infomation.\n");
                        exit (-1);
                }

                strncpy(cacrt, certificate, pos - certificate); 
                pos1 = pos + 1;
                pos = strstr(pos1, ":");
                if (pos1 == NULL) {
                        log_err("Invalid ssl certificate infomation.\n");
                        exit (-1);
                }
                strncpy(srvcrt, pos1, pos - pos1); 
                pos++;
                strcpy(srvkey, pos);

                if (strlen(cacrt) <= 0 || strlen(srvcrt) <= 0 || strlen(srvkey) <= 0) {
                        log_err("Invalid ssl certificate.\n");
                        exit (0);
                }
        }
        
        /* get address */
        pos = strstr(server_address, ":"); 
        if (pos == NULL) {
               log_err("Invalid server address infomation.\n");
               exit(-1);
        }

        strncpy(bindip, server_address, pos - server_address);
        bindport = atoi(++pos);

        if (strlen(bindip) <= 0 || bindport <= 0) {
                log_err("Invalid server ip or port.\n");
                exit(-1);
        }

        printf("\n------Echoserver start infomation------\n"
               "server address: %s\n"
               "server transport: %s\n"
               "server ssl certificate: %s\n"
               "-------Exit Ctrl+C-------------------\n\n",
               server_address, transport, certificate);

        if (use_udp_transport) {
                datagramslot = aio_slot_open(base, bindip, bindport, AIO_UDP);
                if (datagramslot == NULL) {
                        log_err("Failed to open a udp slot.\n");
                        exit(-1);
                }
                SLOTUSRP(datagramslot) = &shutdown;
                aio_setclose(datagramslot, onclosed);
                if (aio_setread(datagramslot, onreceived, 1, 0) < 0) {
                        log_err("Failed to set aio read.\n");
                        exit(-1);
                }
        } 


        if (use_tcp_transport) {
                streamslot = aio_slot_open(base, bindip, bindport, AIO_TCP);
                if (streamslot == NULL) {
                        log_err("Failed to open a slot.\n");
                        exit(-1);
                }
                SLOTUSRP(streamslot) = &shutdown;
                aio_setclose(streamslot, onclosed);
                if (aio_listen(streamslot, onaccepted) < 0) {
                        log_err("Failed to call aio listen function.\n");
                        exit(-1);
                }
        }

        if (use_tls_transport) {
#ifdef _AIO_USE_OPENSSL
                streamslot = aio_slot_open(base, bindip, bindport, AIO_TLS);
                if (streamslot == NULL) {
                        log_err("Failed to open a slot.\n");
                        exit(-1);
                }
                SLOTUSRP(streamslot) = &shutdown;
                aio_setclose(streamslot, onclosed);
                if (aio_tlslisten(streamslot, onaccepted, cacrt, srvcrt, srvkey) < 0) {
                        log_err("Failed to call aio listen function.\n");
                        exit(-1);
                }
#endif
       }

#if 0 /* test for aio timer. */
        aio_timer_set(base, 1000, ontimer1, NULL);
        aio_timer_set(base, 2000, ontimer2, NULL);  
#endif

        while (!*(unsigned short *)ABUSRP(base)) {
                aio_loop(base, 1000); /* Loop per 1 second */
        }

        aio_destroy(base, 1);

        log_info("Echo server exit.\n");
        exit(0);
}

